package com.jeffgalper.feather;

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Arrays;

public class Feather implements Closeable {
   static final int BITS_PER_BYTE = 8;
   static String MAGIC_FEATHER_STRING = "FEATHER";
   static ByteBuffer MAGIC_FEATHER_BB;
   static short VERSION = 3;
   static ByteBuffer VERSION_BB;
   static {
      VERSION_BB = ByteBuffer.allocate(Short.SIZE / BITS_PER_BYTE);
      VERSION_BB.putShort(VERSION);
      try {
         MAGIC_FEATHER_BB = ByteBuffer.wrap(MAGIC_FEATHER_STRING.getBytes("US-ASCII"));
      } catch (UnsupportedEncodingException e) {
         throw new RuntimeException("Could not encode magic number correctly",
               e);
      }
   }
   private File mFile;
   private FileInputStream mIN = null;
   private FileChannel mINFC = null;

   public Feather(String destFilename) {
      mFile = new File(destFilename);
   }

   public void addFeatherable(Featherable featherable) throws IOException,
         FeatherException {
      FileOutputStream out = null;
      FileChannel fc = null;
      try {
         out = new FileOutputStream(mFile, true);
         fc = out.getChannel();
         addFeatherableToOpenFC(featherable, fc);
      } finally {
         if (fc != null)
            fc.close();
         if (out != null)
            out.close();
      }
   }

   private void addFeatherableToOpenFC(Featherable featherable, FileChannel fc)
         throws IOException, FeatherException {
      long start = fc.position();
      MAGIC_FEATHER_BB.rewind();
      VERSION_BB.rewind();
      fc.write(new ByteBuffer[] { MAGIC_FEATHER_BB, VERSION_BB,
            featherable.getEPathSizeBytes(), featherable.getEPathBytes(),
            featherable.getBitField() });
      if (!featherable.isDirectory()) {
         fc.write(featherable.getFileSizeBytes());
         featherable.write(fc);
      }
      int padding = 512 - (int) ((fc.position() - start) % 512);
      if (padding > 0) {
         ByteBuffer paddingBB = ByteBuffer.allocate(padding);
         fc.write(paddingBB);
      }
      if (featherable.isDirectory()) {
         for (Featherable f : featherable.getChildren())
            addFeatherableToOpenFC(f, fc);
      }
   }

   public void addFile(String filename) throws IOException, FeatherException {
      addFeatherable(new FeatherableFile(filename));
   }

   public boolean hasFeathered() throws IOException {
      getInputs();
      if (mINFC.position() == mINFC.size())
         return false;
      ByteBuffer magic = ByteBuffer.allocate(MAGIC_FEATHER_BB.capacity());
      while (magic.position() < magic.capacity()) {
         mINFC.read(magic, mINFC.position() + magic.position());
      }
      magic.flip();
      return Arrays.equals(MAGIC_FEATHER_BB.array(), magic.array());
   }

   public Feathered getFeathered() throws IOException {
      if (!hasFeathered())
         throw new IOException("No feathered file on input");
      return new FeatheredFileChannel(mINFC);
   }

   private void getInputs() throws IOException {
      if (mIN == null || mINFC == null) {
         assert mIN == null && mINFC == null;
         mIN = new FileInputStream(mFile);
         mINFC = mIN.getChannel();
      }
   }

   public void close() throws IOException {
      if (mINFC != null)
         mINFC.close();
      if (mIN != null)
         mIN.close();
   }

}
